﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using up6.db.utils;

namespace up6.db.store
{
    /// <summary>
    /// AWS签名算法V4
    /// https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
    /// CreateMultipartUpload
    /// https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_CreateMultipartUpload.html
    /// PutObject
    /// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
    /// </summary>
    public class MinioAuthorization
	{
		private MinioConfig cfg;
		private string timeCur = "";//20220519
		private string timeIso = "";
		private string method = "PUT";//PUT,GET,POST
		private string uri = "";//资源路径,/test/test.txt
		private string host = "";
		public string data_sha256_Hash = "";//内容-sha256   HexEncode(Hash(RequestPayload))
		public string dataMd5 = "";
		public int contentLength = 0;
		private DateTime timeNow = DateTime.UtcNow;
		private string contentType = "";
		private Dictionary<string, string> m_headers = new Dictionary<string, string>();
		private string CanonicalQueryString;//请求字符串

		public MinioAuthorization()
		{
			this.cfg = new MinioConfig();
			this.timeIso = this.timeNow.ToString("yyyyMMddTHHmmssZ");
			this.timeCur = this.timeNow.ToString("yyyyMMdd");

			//header
			this.m_headers.Add("content-length", "");
			this.m_headers.Add("content-type", "");
			this.m_headers.Add("host", "");
			this.m_headers.Add("content-md5", "");
			this.m_headers.Add("x-amz-content-sha256", "");
			this.m_headers.Add("x-amz-date", "");
		}

		public MinioAuthorization setConfig(MinioConfig v)
		{
			this.cfg = v;
			return this;
		}


		private String getHeaders()
		{
			string head = this.CanonicalHeaders();
			string[] arr = head.Split('\n');
			List<string> heads = new List<string>();

			foreach (string a in arr)
			{
				if (!string.IsNullOrEmpty(a))
				{
					int end = a.Length - 1;
					if (a.IndexOf(":") != -1) end = a.IndexOf(":");
					heads.Add(a.Substring(0, end));
				}
			}
			head = string.Join(";", heads.ToArray());
			return head;
		}


		public MinioAuthorization setMethod(String v)
		{
			this.method = v;
			return this;
		}

		public MinioAuthorization setData(byte[] v)
		{
			this.contentLength = v.Length;
			this.data_sha256_Hash = Md5Tool.base16(Md5Tool.sha256(v));
			//System.Diagnostics.Debug.WriteLine("内容-sha256-hash：\n" + this.data_sha256_Hash);
			this.dataMd5 = Md5Tool.calc(v);
			this.dataMd5 = this.dataMd5.Substring(8, 24);
			var arr = System.Text.Encoding.UTF8.GetBytes(this.dataMd5);
			this.dataMd5 = Convert.ToBase64String(arr);
			return this;
		}

		public String getDataHash() { return this.data_sha256_Hash; }
		public MinioAuthorization setUrl(String v)
		{
			Uri u = new Uri(v);
			this.host = u.Host;
			if (u.Port != 80)
			{
				this.host += ":";
				this.host += u.Port;
			}

			this.setUri(u.LocalPath);
			this.setQueryString(u.Query);
			return this;
		}
		public MinioAuthorization setUri(String v)
		{
			this.uri = PathTool.url_safe_encode(v);
			return this;
		}
		public MinioAuthorization setHost(String h)
		{
			this.host = h;
			return this;
		}
		public MinioAuthorization setContentType(String v)
		{
			this.contentType = v;
			return this;
		}
		public MinioAuthorization setQueryString(String v)
		{
			if (null == v) return this;
			if (string.IsNullOrEmpty(v)) return this;
			//?uploads -> uploads
			int pos = v.IndexOf("?");
			if (-1 != pos) v = v.Substring(pos + 1);

			string[] arr = v.Split('&');
			List<string> qs = new List<string>();
			foreach (string q in arr)
			{
				string a = q;
				//所有参数后面必须跟=
				if (q.IndexOf("=") == -1) a += "=";
				qs.Add(a);
			}

			this.CanonicalQueryString = string.Join("&", qs.ToArray());
			return this;
		}

		public MinioAuthorization delHead(String n)
		{
			this.m_headers.Remove(n.ToLower());
			return this;
		}

		public MinioAuthorization addHead(String n, String v)
		{
			this.m_headers.Add(n, v);
			return this;
		}

		public String getTimeIso()
		{
			return this.timeIso;
		}

		byte[] HmacSHA256(String data, byte[] key)
		{
			String algorithm = "HmacSHA256";
			KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
			kha.Key = key;

			return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
		}

		/**
		 * 签名密钥 
		 * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-calculate-signature.html
		 * 	AWS4-HMAC-SHA256
			20150830T123600Z
			20150830/us-east-1/iam/aws4_request
			示例签名密钥
				f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59
		 * @return
		 * @throws Exception
		 */
		byte[] getSignatureKey()
		{
			byte[]
			kSecret = System.Text.Encoding.UTF8.GetBytes("AWS4" + this.cfg.sk);
			byte[]
			kDate = HmacSHA256(this.timeCur, kSecret);
			byte[]
			kRegion = HmacSHA256(this.cfg.region, kDate);
			byte[]
			kService = HmacSHA256(this.cfg.service, kRegion);
			byte[]
			kSigning = HmacSHA256("aws4_request", kService);

			return kSigning;
		}

		/**
		 * 规范标头
		 * @return
		 */
		string CanonicalHeaders()
		{
			string[] arr = {
				"content-length:"+this.contentLength,
				//"content-md5:"+this.dataMd5,
				"content-type:"+this.contentType,
				"host:"+this.host,
				"x-amz-content-sha256:"+this.data_sha256_Hash,
				//"x-amz-content-sha256:"+this.data_sha256_Hash,
				"x-amz-date:"+this.timeIso
		};

			//设置header值
			if (this.m_headers.ContainsKey("content-length"))
				this.m_headers["content-length"] = this.contentLength.ToString();
			if (this.m_headers.ContainsKey("content-type"))
				this.m_headers["content-type"] = this.contentType;
			if (this.m_headers.ContainsKey("host"))
				this.m_headers["host"] = this.host;
			if (this.m_headers.ContainsKey("content-md5"))
				this.m_headers["content-md5"] = this.dataMd5;
			if (this.m_headers.ContainsKey("x-amz-content-sha256"))
				this.m_headers["x-amz-content-sha256"] = this.data_sha256_Hash;
			if (this.m_headers.ContainsKey("x-amz-date"))
				this.m_headers["x-amz-date"] = this.timeIso;

			List<string> hs = new List<string>();
			foreach (KeyValuePair<string, string> k in this.m_headers)
			{
				hs.Add(k.Key + ":" + k.Value);
			}
			hs.Sort();

			string str = String.Join("\n", hs.ToArray()) + "\n";
			//Debug.WriteLine("规范标头："+str);
			return str;
		}

		/**
		 * 凭证范围值，
		 * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
		 * 示例：
		 * 	20150830/us-east-1/iam/aws4_request
		 * @return
		 */
		String CredentialScope()
		{
			string[] arr = {
				this.timeCur,
				this.cfg.region,
				"s3/aws4_request"
		};
			string str = String.Join("/", arr);
			//Debug.WriteLine("凭证范围：\n"+str);

			return str;
		}

		/// <summary>
		/// 
		/**
			* 规范请求头签名
			* https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-canonical-request.html
			* 格式：
			* CanonicalRequest =
				HTTPRequestMethod + '\n' +
				CanonicalURI + '\n' +
				CanonicalQueryString + '\n' +
				CanonicalHeaders + '\n' +
				SignedHeaders + '\n' +
				HexEncode(Hash(RequestPayload))
			* @return
			*/
		/// </summary>
		/// <returns></returns>
		string HashCanonicalRequest()
		{
			string[] arr = {
				this.method,
				this.uri,
				this.CanonicalQueryString,//请求字符串
				this.CanonicalHeaders(),//请求头参数
				this.getHeaders(),//签名头
				this.data_sha256_Hash//内容-sha256
		};

			string str = String.Join("\n", arr);
			//Debug.WriteLine("规范请求头：\n" + str);
			str = Md5Tool.sha256(str);
			//Debug.WriteLine("规范请求头签名：\n" + str);
			return str;
		}

		/**
		 * 待签名字符串
		 * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
		 * 格式：
		 * StringToSign =
			Algorithm + \n +
			RequestDateTime + \n +
			CredentialScope + \n +
			HashedCanonicalRequest
		 * @return
		 */
		private string StringSign()
		{
			string[] arr = {
				this.cfg.algorithm,
				this.timeIso,
				this.CredentialScope(),
				this.HashCanonicalRequest()
		};
			//待签名字符串
			string str = string.Join("\n", arr);

			//Debug.WriteLine("待签名字符串：\n" + str);
			//计算签名密钥
			byte[] key = this.getSignatureKey();
			//Debug.WriteLine("签名密钥：" + Md5Tool.base16(key));

			//签名
			str = Md5Tool.base16(this.HmacSHA256(str, key));
			//Debug.WriteLine("密钥编码：" + str);

			return str;
		}

		/**
		 * 待签名字符串
		 * @return
		 */
		public string Credential()
		{
			string[] arr = {
				this.cfg.ak,//
				this.timeCur,//时间：20220519
				this.cfg.region,//region
				this.cfg.service,//服务
				"aws4_request"
		};
			string v = string.Join("/", arr);
			//System.out.println("待签名字符串："+v);
			return v;
		}

		/**
		 * Authorization 标头,添加到Header中
		 * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-add-signature-to-request.html
		 * 格式：
		 * 	Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature
		 * 示例：
		 * 	Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
		 * @return
		 */
		public String Authorization()
		{
			return this.cfg.algorithm + " " +
					"Credential=" + this.Credential() + ", " +
					"SignedHeaders=" + this.getHeaders() + ", " +//签名头
					"Signature=" + this.StringSign();
		}
	}
}